<template>
  <div class="table-main">
    <ElTable
      ref="elTable"
      class="table-main-table"
      stripe
      border
      highlightCurrentRow
      v-bind="tablePropsFinal"
      :maxHeight="tableMaxHeight"
      :data="list"
      :rowKey="assertRowKey(rowKey)"
    >
      <ElTableColumn
        v-for="column of columns"
        :key="column.columnKey || column.prop"
        v-bind="column"
      >
        <template v-if="isShowSlots(column.type)" #default="scopeData">
          <slot
            :name="column.prop"
            v-bind="assertColumnDefaultScope(scopeData)"
          >
            <template v-if="column.render">
              <component
                :is="column.render"
                v-bind="assertColumnDefaultScope(scopeData, ['$index'])"
                :_index="scopeData.$index"
              ></component>
            </template>
            <template v-else>
              {{ scopeData.row[column.prop] }}
            </template>
          </slot>
        </template>
        <template v-if="isShowSlots(column.type)" #header="scopeData">
          <slot
            :name="`header-${column.prop}`"
            v-bind="assertColumnHeaderScope(scopeData)"
          >
            <template v-if="column.headerRender">
              <component
                :is="column.headerRender"
                v-bind="assertColumnHeaderScope(scopeData)"
              ></component>
            </template>
            <template v-else>
              {{ column.label }}
            </template>
          </slot>
        </template>
      </ElTableColumn>
      <template #empty>
        <template v-if="loading">加载中...</template>
        <template v-else>
          <ElEmpty />
        </template>
      </template>
    </ElTable>
    <div v-show="showPager" class="table-main-pagination">
      <ElPagination
        v-model:current-page="currentPage"
        v-model:page-size="pageSize"
        :page-sizes="pageSizeOptions"
        small
        background
        :layout="pageLayout"
        :total="total"
        @size-change="onSizeChange"
      />
    </div>
  </div>
</template>
<script
  lang="ts"
  setup
  generic="
    T extends Record<string, any>,
    SQ extends Record<string, any>,
    F extends Record<string, any>
  "
>
import type { UnwrapRef } from "vue";
import { BODY_CONTENT_VIEWPORT_HEIGHT } from "@/utils";
import type {
  TableMainProps,
  TableMainInstance,
  TableApiParams,
  TableColumnDefaultScope,
  TableColumnHeaderScope,
  ElTableColumnProps,
} from "./types";
import _cloneDeep from "lodash/cloneDeep";
import type { TableInstance } from "element-plus";
import { listCurrentPageKey, listPageSizeKey } from "@/config";
import _omit from "lodash/omit";

const props = withDefaults(defineProps<TableMainProps<T, SQ, F>>(), {
  showPager: true,
  pageSizeInit: 20,
  pageLayout: "total, sizes, prev, pager, next, jumper",
  pageSizeOptions: () => [10, 20, 30, 40],
  headerColor: "rgb(240, 240, 240)",
  refine: true,
  refineReduceHeight: 0,
});

const emits = defineEmits<{
  (e: "pageChange" | "pageSizeChange", value: number): void;
  (e: "dataChange", data: T[]): void;
  (e: "loadingChange", value: boolean): void;
}>();

const attrs = useAttrs();

const list = ref<T[]>([]);

const total = ref(0);

const pageSize = ref(0);

const currentPage = ref(1);

const loading = ref(false);
/** 对外loading */
const publicLoading = ref(false);

const elTable = shallowRef<TableInstance>();

const bodyContentViewportHeight: Ref<number> = inject(
  BODY_CONTENT_VIEWPORT_HEIGHT,
  500,
) as unknown as Ref<number>;

const pagerHeightAdjust = computed(() => {
  if (props.showPager) {
    return 35;
  } else {
    return 0;
  }
});

const tableMaxHeight = computed(() => {
  const { maxHeight, refine } = props;
  if (maxHeight) {
    return Math.max(maxHeight - pagerHeightAdjust.value, 200);
  } else {
    if (refine) {
      return Math.max(
        bodyContentViewportHeight.value -
          pagerHeightAdjust.value -
          props.refineReduceHeight,
        200,
      );
    } else {
      return undefined;
    }
  }
});

const params = computed<TableApiParams<SQ>>(() => {
  return {
    ...(props.query! || {}),
    [listPageSizeKey]: pageSize.value,
    [listCurrentPageKey]: currentPage.value,
  };
});

const tablePropsFinal = computed(() => {
  return {
    ...props.tableProps,
    ...attrs,
  };
});

/** 是否可以通过参数继续前进 */
const canGoOnByParams = (currentParams: TableApiParams<SQ>, tip = true) => {
  const res = JSON.stringify(currentParams) === JSON.stringify(params.value);
  if (!res && tip) {
    console.warn(
      `同一地方 并发请求 参数冰花 当前不是最新的参数 丢弃结果 ${JSON.stringify(currentParams)} => ${JSON.stringify(params.value)}`,
    );
  }
  return res;
};

/**
 * 获取列表数据
 * @param silent 是否静默加载
 */
const getList = (silent = false) => {
  if (!pageSize.value) return;
  if (loading.value) {
    // 此处不return 只警告
    console.warn(`请求并发`);
  } else {
    loading.value = true;
  }

  if (!silent) {
    publicLoading.value = true;
  }

  const p = _cloneDeep(params.value);

  props
    .api(p)
    .then((res) => {
      if (!canGoOnByParams(p, false)) {
        return;
      }
      list.value = res.list as UnwrapRef<T[]>;
      total.value = res.total;
    })
    .finally(() => {
      if (!canGoOnByParams(p)) {
        return;
      }
      loading.value = false;
      publicLoading.value = false;
    });
};

const refresh: TableMainInstance["refresh"] = (silent = false) => {
  getList(silent);
};

const reload: TableMainInstance["reload"] = (silent = false) => {
  // console.log("重新加载");
  if (currentPage.value === 1) {
    refresh(silent);
  } else {
    currentPage.value = 1;
  }
};

/** TODO: vue 模版是基于html解析 使用as断言会有问题 此处先使用纯函数断言 后面通过terser配置处理这种内联函数 */
const assertRowKey = (rowKey: any) => {
  return rowKey as string | ((row: T) => string);
};

const expose: TableMainInstance = {
  refresh,
  reload,
  getTableInstance: () => elTable.value,
};

/** TODO: vue 模版是基于html解析 使用as断言会有问题 此处先使用纯函数断言 后面通过terser配置处理这种内联函数 */
const assertColumnDefaultScope = (scope: any, omitKeys: string[] = []) => {
  return {
    ..._omit(scope, ["render", "headerRender"].concat(omitKeys)),
    injectInfo: props.injectInfo || {},
    exposeInfo: expose,
  } as unknown as TableColumnDefaultScope<T, F>;
};

/** TODO: vue 模版是基于html解析 使用as断言会有问题 此处先使用纯函数断言 后面通过terser配置处理这种内联函数 */
const assertColumnHeaderScope = (scope: any) => {
  return {
    ..._omit(scope, ["render", "headerRender"]),
    injectInfo: props.injectInfo || {},
    exposeInfo: expose,
  } as unknown as TableColumnHeaderScope<T, F>;
};

defineExpose(expose);

const onSizeChange = (value: number) => {
  pageSize.value = value;
  currentPage.value = 1;
};

// const onCurrentChange = (value: number) => {
//   console.log("当前页", value);
// };

// 展示slots
const isShowSlots = (type: ElTableColumnProps["type"]) => {
  // 不配置type 或者 type配置为expand
  return !type || type === "expand";
};

onMounted(() => {
  pageSize.value = props.pageSizeInit;

  onActivated(() => {
    expose.refresh();
  });
});

watch(list, (n) => {
  emits("dataChange", n as T[]);
});

watch(
  () => props.query,
  (n, o) => {
    if (JSON.stringify(n) === JSON.stringify(o)) return;
    // console.log("外部query变化 重新从第一页加载", n);
    // reload();
    currentPage.value = 1;
  },
);

watch(
  params,
  (n, o) => {
    if (JSON.stringify(n) === JSON.stringify(o)) return;
    // console.log("外部query或分页参数有变化", n);
    getList();
  },
  {
    immediate: true,
  },
);

watch(publicLoading, (n) => {
  emits("loadingChange", n);
});

watch(pageSize, (n) => {
  emits("pageSizeChange", n);
});

watch(currentPage, (n) => {
  emits("pageChange", n);
});
</script>
<style lang="less" scoped>
.table-main {
  &-table {
    --el-table-header-bg-color: v-bind(headerColor);
  }
  &-pagination {
    margin-top: 10px;
    display: flex;
    justify-content: flex-end;
  }
}
</style>
